﻿//---------------------------------------------------------------------------------------------------------------------
// <copyright file="StudioContentRights.cs" company="blinkBox Entertainment Ltd.">
//   Copyright (c) blinkBox Entertainment Ltd. All rights reserved.
// </copyright>
// <summary>
//   The content rights for a Studio or individual ContentAsset.
// </summary>
//---------------------------------------------------------------------------------------------------------------------

namespace BlinkBox.Treadstone.Media
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    using BlinkBox.Data.Serialization;
    using BlinkBox.Diagnostics;

    #endregion

    /// <summary>
    /// The content rights for a <see cref="Studio"/> or individual <see cref="ContentAsset"/>.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Rights for content from a <see cref="Studio"/> may be defined at various levels. These include distribution 
    /// studio, production studio, or an individual <see cref="ContentAsset"/>. They also vary by the each type of 
    /// <see cref="ContentPart"/> contained within the asset, the country that the content is being viewed or used in, 
    /// and the <see cref="RightsZone"/> of the calling application. This class encapsulates the rights applicable to 
    /// either a <see cref="Studio"/> or <see cref="ContentAsset"/> in a particular country.
    /// </para>
    /// <para>
    /// Because the rights are defined at various levels, there is no single property indicating what rights are applicable
    /// for a <see cref="ContentAsset"/>. Instead there are a number of methods on this class which allow the effective 
    /// rights to be calculated based on the context. A number of examples are given below, and on the methods themselves.
    /// </para>
    /// <para>
    /// This class covers the following rights, as detailed in the Studio Rights Management v3.0 specification: SR_02, SR_03,
    /// SR_04, SR_08, SR_10, SR_11, SR_12, SR_14, SR_18, SR_42 and SR_72. The following rights are not covered by this class 
    /// as they are not applicable to content: SR_05, SR_06, SR_07, SR_09, SR_15, SR_19 and SR_20. The following rights are 
    /// not covered by this class as they are insufficiently defined and so cannot be implemented: SR_13, SR_16, SR_17, SR_21, 
    /// SR_22, SR_23 and SR_24.
    /// </para>
    /// </remarks>
    /// <example>
    /// <para>
    /// These examples show how common scenarios from the Studio Rights Management v3.0 specification can be implemented
    /// using this class. In all cases it is assumed that there is a <see cref="ContentAsset"/> that has already been
    /// retrieved, <c>contentAsset</c>, and a sequence of its parts, <c>contentParts</c>.
    /// </para>
    /// <para>
    /// <strong>SR_02</strong> - On-site, allow users to watch Content; <strong>SR_03</strong> - Off-site (trusted domain), 
    /// allow users to watch Content; <strong>SR_04</strong> - Off-site (non-trusted domain), allow users to watch Content; 
    /// <strong>SR_08</strong> - Allow the provision of Scene Embed code to users. 
    /// </para>
    /// <para>
    /// Although SR_04 and SR_08 are specified as different rights, they are actually identical as the right to watch content 
    /// off-site implies the right to embed it, and vice versa. To determine whether content may be embedded the check is
    /// for the <see cref="ContentRights.Watch"/> right in the <see cref="RightsZone.Internet"/> zone.
    /// </para>
    /// <code>
    /// <![CDATA[ 
    /// // set the rights zone to be either BlinkBox (on-site), Trusted (off-site trusted), or Internet (off-site untrusted)
    /// // for this example it is assumed that we are working out the permitted rights on-site
    /// var rightsZone = RightsZone.BlinkBox; 
    /// 
    /// // to determine whether the user is permitted to watch any trailers, clips or extras from the asset; this may be
    /// // used when listing content to decide whether to allow the user to click through to a detailed view
    /// var relevantPartTypes = new[] { ContentPartType.Trailer, ContentPartType.Clip, ContentPartType.Extra };
    /// contentAsset.Rights.HasAvailableRights(ContentRights.Watch, rightsZone, relevantPartTypes);
    /// 
    /// // to determine which part types the user is permitted to watch; this may be used to restrict the parts that are
    /// // shown to the user when they attempt to watch the content
    /// var watchablePartTypes = contentAsset.Rights.GetPartTypesHavingRights(ContentRights.Watch, rightsZone);
    /// 
    /// // to filter a list of parts to only those the user is permitted to watch; this is an easier way to restrict the
    /// // parts that are shown to the user when they attempt to watch the content
    /// var watchableParts = contentAsset.Rights.GetPartsHavingRights(contentParts, ContentRights.Watch, rightsZone);
    /// ]]>
    /// </code>
    /// <para>
    /// <strong>SR_10</strong> - Allow users to blink from content. 
    /// </para>
    /// <code>
    /// <![CDATA[ 
    /// // set the rights zone to be either BlinkBox (on-site), Trusted (off-site trusted), or Internet (off-site untrusted)
    /// // for this example it is assumed that we are working out the permitted rights on-site
    /// var rightsZone = RightsZone.BlinkBox; 
    /// 
    /// // to determine whether the user is permitted to blink from any trailers, clips or extras from the asset; this may
    /// // be used to decide whether to show action buttons/links to prompt the user to create a blink
    /// var relevantPartTypes = new[] { ContentPartType.Trailer, ContentPartType.Clip, ContentPartType.Extra };
    /// var canBlinkFromAsset = contentAsset.Rights.HasAvailableRights(ContentRights.CreateBlink, rightsZone, relevantPartTypes);
    /// 
    /// // to determine whether the user is permitted to blink from a selected part; although when only one part type is 
    /// // selected HasSharedRights and HasAvailableRights return the same answer, it is good practice to use HasSharedRights
    /// // when restricting permissions because it always returns a result applicable to any passed in part type whereas
    /// // HasAvailableRights makes no such guarantee
    /// var selectedPartType = ContentPartType.Clip;
    /// var canBlinkFromSelectedPart = contentAsset.Rights.HasSharedRights(ContentRights.CreateBlink, rightsZone, selectedPartType);
    /// ]]>
    /// </code>
    /// <para>
    /// <strong>SR_11</strong> - Allow users to send Blinks made from content to mobile.
    /// </para>
    /// <para>
    /// Although not specified in the requirements, the right to send blinks from content to email is also implemented
    /// as a discrete right, and should not be assumed to always be present.
    /// </para>
    /// <code>
    /// <![CDATA[ 
    /// // set the rights zone to be either BlinkBox (on-site), Trusted (off-site trusted), or Internet (off-site untrusted)
    /// // for this example it is assumed that we are working out the permitted rights on-site
    /// var rightsZone = RightsZone.BlinkBox; 
    /// 
    /// // to determine whether the user is permitted to send a blink made from a particular part type to email
    /// var blinkedFromPartType = ContentPartType.Trailer;
    /// var canSendToEmail = contentAsset.Rights.HasSharedRights(ContentRights.SendBlinkToEmail, rightsZone, blinkedFromPartType);
    /// 
    /// // to determine whether the user is permitted to send a blink made from a particular part type to mobile
    /// var blinkedFromPartType = ContentPartType.Clip;
    /// var canSendToMobile = contentAsset.Rights.HasSharedRights(ContentRights.SendBlinkToMobile, rightsZone, blinkedFromPartType);
    /// 
    /// // to determine whether the user is permitted to send a blink made from a particular part type to either email or mobile
    /// var blinkedFromPartType = ContentPartType.Extra;
    /// var canSendToEmail = contentAsset.Rights.HasAvailableRights(
    ///     ContentRights.SendBlinkToEmail | ContentRights.SendBlinkToMobile, 
    ///     rightsZone, 
    ///     blinkedFromPartType);
    /// ]]>
    /// </code>
    /// <para>
    /// <strong>SR_12</strong> - Allow use of content in Games / Quizzes.
    /// </para>
    /// <code>
    /// <![CDATA[ 
    /// // set the rights zone to be either BlinkBox (on-site), Trusted (off-site trusted), or Internet (off-site untrusted)
    /// // for this example it is assumed that we are working out the permitted rights on-site
    /// var rightsZone = RightsZone.BlinkBox; 
    /// 
    /// // to determine whether the user is permitted to use any trailers, clips or extras from the asset
    /// var relevantPartTypes = new[] { ContentPartType.Trailer, ContentPartType.Clip, ContentPartType.Extra };
    /// contentAsset.Rights.HasAvailableRights(ContentRights.UseInApplets, rightsZone, relevantPartTypes);
    /// 
    /// // to determine which part types the user is permitted to use
    /// var usablePartTypes = contentAsset.Rights.GetPartTypesHavingRights(ContentRights.UseInApplets, rightsZone);
    /// 
    /// // to filter a list of parts to only those the user is permitted to use
    /// var usableParts = contentAsset.Rights.GetPartsHavingRights(contentParts, ContentRights.UseInApplets, rightsZone);
    /// ]]>
    /// </code>
    /// <para>
    /// <strong>SR_14</strong> - Allow on-site pre-roll ads to be served in Content; <strong>SR_18</strong> - Allow off-site 
    /// pre-roll ads to be served in Content.
    /// </para>
    /// <code>
    /// <![CDATA[ 
    /// // set the rights zone to be either BlinkBox (on-site), Trusted (off-site trusted), or Internet (off-site untrusted)
    /// // for this example it is assumed that we are working out the permitted rights on-site
    /// var rightsZone = RightsZone.BlinkBox; 
    /// 
    /// // to determine which part types that pre-roll adverts may be served before
    /// var preRollPartTypes = contentAsset.Rights.GetPartTypesHavingRights(ContentRights.PreRoll, rightsZone);
    /// ]]>
    /// </code>
    /// <para>
    /// <strong>SR_42</strong> - Allow users to DTO/DTR.
    /// </para>
    /// <code>
    /// <![CDATA[ 
    /// // set the rights zone to be either BlinkBox (on-site), Trusted (off-site trusted), or Internet (off-site untrusted)
    /// // for this example it is assumed that we are working out the permitted rights on-site
    /// var rightsZone = RightsZone.BlinkBox; 
    /// 
    /// // to determine whether the user is permitted to download the feature part which is the only relevant part type
    /// // when considering permission to download to own or rent
    /// var canDownload = contentAsset.Rights.HasSharedRights(ContentRights.Download, rightsZone, ContentPartType.Feature);
    /// ]]>
    /// </code>
    /// <para>
    /// <strong>SR_72</strong> - Allow users to watch for free.
    /// </para>
    /// <para>
    /// Although SR_72 specifies that the right is to watch for free, this does not quite reflect the implementation. What
    /// is implemented is the right to watch the content, and then the price and availability of this is governed by the
    /// presence of chapters and products for this.
    /// </para>
    /// <code>
    /// <![CDATA[ 
    /// // set the rights zone to be either BlinkBox (on-site), Trusted (off-site trusted), or Internet (off-site untrusted)
    /// // for this example it is assumed that we are working out the permitted rights on-site
    /// var rightsZone = RightsZone.BlinkBox; 
    /// 
    /// // to determine whether the user is permitted to watch the chapters (which are the parts that when combined make
    /// // up the entire content) online
    /// var canWatchChapters = contentAsset.Rights.HasSharedRights(ContentRights.Watch, rightsZone, ContentPartType.Chapter);
    /// ]]>
    /// </code>
    /// </example>
    [Serializable]
    [DebuggerDisplay("StudioId = {StudioId}, ContentAssetId = {ContentAssetId}, CountryCode = {CountryCode}")]
    public sealed class StudioContentRights
    {
        #region Constants and Fields

        /// <summary>
        /// A dictionary of rights by zone. Key is the zone in which the rights apply, and value is a dictionary mapping
        /// the type of content part to its rights. This field is read-only.
        /// </summary>
        private readonly Dictionary<RightsZone, Dictionary<ContentPartType, ContentRights>> rightsByZone;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new <see cref="StudioContentRights"/> with specified list of <see cref="StudioContentZoneRights"/>.
        /// </summary>
        /// <param name="zoneRights">
        /// A sequence of <see cref="StudioContentZoneRights"/> which must have the same <see cref="StudioContentZoneRights.StudioId"/>,
        /// <see cref="StudioContentZoneRights.CountryCode"/> and <see cref="StudioContentZoneRights.ContentAssetId"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="zoneRights"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// Not all of the <see cref="StudioContentZoneRights"/> in <paramref name="zoneRights"/> have the same <see cref="StudioContentZoneRights.StudioId"/>,
        /// <see cref="StudioContentZoneRights.CountryCode"/> and <see cref="StudioContentZoneRights.ContentAssetId"/>.
        /// </exception>
        public StudioContentRights(IEnumerable<StudioContentZoneRights> zoneRights)
        {
            BlinkBoxTrace.In(zoneRights);
            RequireArgument.IsNotNull("zoneRights", zoneRights);
            RequireArgument.MeetsCondition("zoneRights", zoneRights.Any(), Resources.SequenceIsEmpty);

            // runtime checks for data integrity that is critical to the operation of the class
            var studioIds = zoneRights.Select(rights => rights.StudioId).Distinct();
            if (studioIds.CountGreaterThan(1))
            {
                throw new ArgumentException(Resources.SequenceContainsRightsForMoreThanOne.FormatWith("Studio"), "zoneRights");
            }

            var countryCodes = zoneRights.Select(rights => rights.CountryCode).Distinct();
            if (countryCodes.CountGreaterThan(1))
            {
                throw new ArgumentException(Resources.SequenceContainsRightsForMoreThanOne.FormatWith("Country"), "zoneRights");
            }

            var contentAssetIds = zoneRights.Select(rights => rights.ContentAssetId).Distinct();
            if (contentAssetIds.CountGreaterThan(1))
            {
                throw new ArgumentException(Resources.SequenceContainsRightsForMoreThanOne.FormatWith("ContentAsset"), "zoneRights");
            }

            this.StudioId = studioIds.First();
            this.CountryCode = countryCodes.First();
            this.ContentAssetId = contentAssetIds.First();
            this.rightsByZone = zoneRights.ToLookup(rights => rights.Zone).ToDictionary(group => group.Key, group => group.ToDictionary(rights => rights.PartType, rights => rights.Rights));

            // debug-only check ensure the loaded data is valid and complete as we can function without a complete 
            // set of data as long as it meets the entry conditions, but this helps to make sure it is fully 
            // populated during the development and testing phases
            ////Debug.Assert(this.rightsByZone.Count == 3, "Expected rights to be specified for three zones");
            ////Debug.Assert(this.rightsByZone.ContainsKey(RightsZone.Internet), "Expected rights to be specified for the Internet zone");
            ////Debug.Assert(this.rightsByZone.ContainsKey(RightsZone.Trusted), "Expected rights to be specified for the Trusted zone");
            ////Debug.Assert(this.rightsByZone.ContainsKey(RightsZone.BlinkBox), "Expected rights to be specified for the BlinkBox zone");
            ////Debug.Assert(this.rightsByZone[RightsZone.Internet].Count == 5, "Expected rights to be specified for five part types in the Internet zone");
            ////Debug.Assert(this.rightsByZone[RightsZone.Trusted].Count == 5, "Expected rights to be specified for five part types in the Trusted zone");
            ////Debug.Assert(this.rightsByZone[RightsZone.BlinkBox].Count == 5, "Expected rights to be specified for five part types in the BlinkBox zone");

            BlinkBoxTrace.Out();
        }

        #endregion

        #region Properties and Indexers

        /// <summary>
        /// Gets the identifier of the <see cref="ContentAsset"/> to which the rights apply if this is a specific set of rights for an 
        /// individual asset; otherwise zero.
        /// </summary>
        public int ContentAssetId
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the two character ISO 3166-1 Alpha-2 country code for the country in which the rights are valid.
        /// </summary>
        public string CountryCode
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the identifier of the <see cref="Studio"/> the rights relate to.
        /// </summary>
        public short StudioId
        {
            get;
            private set;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the default <see cref="StudioContentRights"/> for a specified <see cref="ContentAsset"/> and country code.
        /// </summary>
        /// <returns>
        /// The default <see cref="StudioContentRights"/>.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="contentAssetId"/> is less than or equal to zero.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="countryCode"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="countryCode"/> is <see cref="string.Empty"/>.
        /// </exception>
        public static StudioContentRights GetDefaultForContentAsset(int contentAssetId, string countryCode)
        {
            BlinkBoxTrace.In(contentAssetId, countryCode);
            RequireArgument.IsGreaterThan("contentAssetId", contentAssetId, 0);
            RequireArgument.IsNotNullOrEmpty("countryCode", countryCode);

            // the default rights we will grant are the Watch right in the BlinkBox zone for trailers/clips/extras as we assume that we wouldn't have
            // any active asset where this was not the case (as it would be somewhat pointless on site) and no rights for anything else
            var zoneRights = new[]
                {
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.Trailer, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.Watch },
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.Clip, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.Watch },
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.Extra, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.Watch },
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.DownloadFeature, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.OnlineFeature, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.Trailer, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.Clip, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.Extra, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.DownloadFeature, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.OnlineFeature, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.Trailer, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.Clip, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.Extra, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.DownloadFeature, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.OnlineFeature, ContentAssetId = contentAssetId, CountryCode = countryCode, Rights = ContentRights.None },
                };

            var rights = new StudioContentRights(zoneRights);

            return BlinkBoxTrace.Out(rights);
        }

        /// <summary>
        /// Gets the default <see cref="StudioContentRights"/> for a specified <see cref="Studio"/> and country code.
        /// </summary>
        /// <returns>
        /// The default <see cref="StudioContentRights"/>.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="studioId"/> is less than or equal to zero.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="countryCode"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="countryCode"/> is <see cref="string.Empty"/>.;
        /// </exception>
        public static StudioContentRights GetDefaultForStudio(short studioId, string countryCode)
        {
            BlinkBoxTrace.In(studioId, countryCode);
            RequireArgument.IsGreaterThan("studioId", studioId, 0);
            RequireArgument.IsNotNullOrEmpty("countryCode", countryCode);

            // the default rights we will grant are the Watch right in the BlinkBox zone for trailers/clips/extras as we assume that we wouldn't have
            // any active asset where this was not the case (as it would be somewhat pointless on site) and no rights for anything else
            var zoneRights = new[]
                {
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.Trailer, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.Watch },
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.Clip, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.Watch },
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.Extra, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.Watch },
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.DownloadFeature, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.BlinkBox, PartType = ContentPartType.OnlineFeature, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.Trailer, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.Clip, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.Extra, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.DownloadFeature, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Trusted, PartType = ContentPartType.OnlineFeature, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.Trailer, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.Clip, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.Extra, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.DownloadFeature, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                    new StudioContentZoneRights { Zone = RightsZone.Internet, PartType = ContentPartType.OnlineFeature, StudioId = studioId, CountryCode = countryCode, Rights = ContentRights.None },
                };

            var rights = new StudioContentRights(zoneRights);

            return BlinkBoxTrace.Out(rights);
        }

        /// <summary>
        /// Gets the available set of <see cref="ContentRights"/> for one or more <see cref="ContentPartType"/>s in a specified 
        /// <see cref="RightsZone"/>; that is the rights that at least one of the part types has.
        /// </summary>
        /// <param name="zone">
        /// The <see cref="RightsZone"/> in which the parts are being used.
        /// </param>
        /// <param name="partTypes">
        /// The <see cref="ContentPartType"/>s to get the effective <see cref="ContentRights"/> for.
        /// </param>
        /// <returns>
        /// The effective <see cref="ContentRights"/> for <paramref name="partTypes"/>.
        /// </returns>
        /// <remarks>
        /// This method calculates the intersection (bitwise OR) of the rights available for the specified part types. The returned 
        /// <see cref="ContentRights"/> are available for at least one of the specified part types, but not necessarily all. It can
        /// be used to determine whether an action is possible in a given context, but further checks must be done per-part type
        /// before the action is performed.
        /// </remarks> 
        /// <exception cref="ArgumentNullException">
        /// <paramref name="partTypes"/> is a null reference.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="zone"/> is not one of <see cref="RightsZone.Internet"/>, <see cref="RightsZone.Trusted"/> or <see cref="RightsZone.BlinkBox"/>.
        /// </exception>
        /// <example>
        /// This example shows how to determine the available set of <see cref="ContentRights"/> for a list of <see cref="ContentPart"/>s 
        /// in the <see cref="RightsZone.BlinkBox"/> security zone.
        /// <code>
        /// <![CDATA[
        /// // the code assumes a list of ContentPart objects, parts, and an instance of StudioContentRights, contentRights  
        /// var zone = RightsZone.BlinkBox;
        /// var partTypes = from part in parts select part.Type;
        /// var availableRights = contentRights.GetAvailableRights(zone, partTypes);
        /// ]]>
        /// </code>
        /// </example>
        public ContentRights GetAvailableRights(RightsZone zone, params ContentPartType[] partTypes)
        {
            BlinkBoxTrace.In(zone, partTypes);
            RequireArgument.IsOneOf("zone", zone, RightsZone.Internet, RightsZone.Trusted, RightsZone.BlinkBox);
            RequireArgument.IsNotNull("partTypes", partTypes);

            var rights = ContentRights.None;
            if (this.rightsByZone.ContainsKey(zone))
            {
                var zoneRights = this.rightsByZone[zone];
                foreach (var partType in partTypes)
                {
                    if (zoneRights.ContainsKey(partType))
                    {
                        rights |= zoneRights[partType];
                    }
                }
            }

            return BlinkBoxTrace.Out(rights);
        }

        /// <summary>
        /// Gets the <see cref="ContentPart"/> objects from a sequence that have the specified <see cref="ContentRights"/> in a 
        /// specified <see cref="RightsZone"/>.
        /// </summary>
        /// <param name="contentParts">
        /// The sequence of <see cref="ContentPart"/>s from which to return the parts having the specified rights.
        /// </param>
        /// <param name="rights">
        /// The <see cref="ContentRights"/> that the parts must have.
        /// </param>
        /// <param name="zone">
        /// The <see cref="RightsZone"/> in which the parts are being used.
        /// </param>
        /// <returns>
        /// A sequence of <see cref="ContentPart"/>s having the specified rights.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="contentParts"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="rights"/> is not a valid combination of <see cref="ContentRights"/>.
        /// <para>- or -</para>
        /// <paramref name="zone"/> is not one of <see cref="RightsZone.Internet"/>, <see cref="RightsZone.Trusted"/> or <see cref="RightsZone.BlinkBox"/>.
        /// </exception>
        /// <example>
        /// This example shows how to filter a list of <see cref="ContentPart"/> objects to only the ones that can be watched in the 
        /// <see cref="RightsZone.BlinkBox"/> security zone.
        /// <code>
        /// <![CDATA[
        /// // the code assumes a list of ContentPart objects, parts, and an instance of StudioContentRights, contentRights  
        /// var rights = ContentRights.Watch;
        /// var zone = RightsZone.BlinkBox; 
        /// var watchableParts = contentRights.GetPartsHavingRights(parts, rights, zone); 
        /// ]]>
        /// </code>
        /// </example>
        public IEnumerable<ContentPart> GetPartsHavingRights(IEnumerable<ContentPart> contentParts, ContentRights rights, RightsZone zone)
        {
            BlinkBoxTrace.In(contentParts, rights, zone);
            RequireArgument.IsNotNull("contentParts", contentParts);
            RequireArgument.IsAnything(rights); // validated by the HasSharedRights method
            RequireArgument.IsAnything(zone);   // validated by the HasSharedRights method

            var partsHavingRights = from contentPart in contentParts where this.HasSharedRights(rights, zone, contentPart.Type) select contentPart;

            return BlinkBoxTrace.Out(partsHavingRights);
        }

        /// <summary>
        /// Gets the <see cref="ContentPartType"/>s that have the specified <see cref="ContentRights"/> in a specified <see cref="RightsZone"/>.
        /// </summary>
        /// <param name="rights">
        /// The <see cref="ContentRights"/> that the parts must have.
        /// </param>
        /// <param name="zone">
        /// The <see cref="RightsZone"/> in which the parts are being used.
        /// </param>
        /// <returns>
        /// A sequence of <see cref="ContentPartType"/>s having the specified rights.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// <paramref name="rights"/> is not a valid combination of <see cref="ContentRights"/>.
        /// <para>- or -</para>
        /// <paramref name="zone"/> is not one of <see cref="RightsZone.Internet"/>, <see cref="RightsZone.Trusted"/> or <see cref="RightsZone.BlinkBox"/>.
        /// </exception>
        /// <example>
        /// This example shows how to filter a list of <see cref="ContentPart"/> objects to only the ones that can be watched in the 
        /// <see cref="RightsZone.BlinkBox"/> security zone.
        /// <code>
        /// <![CDATA[
        /// // the code assumes a list of ContentPart objects, parts, and an instance of StudioContentRights, contentRights  
        /// var rights = ContentRights.Watch;
        /// var zone = RightsZone.BlinkBox; 
        /// var watchablePartTypes = contentRights.GetPartTypesHavingRights(rights, zone); 
        /// var watchableParts = from part in parts where watchablePartTypes.Contains(part.Type) select part;
        /// ]]>
        /// </code>
        /// </example>
        public IEnumerable<ContentPartType> GetPartTypesHavingRights(ContentRights rights, RightsZone zone)
        {
            BlinkBoxTrace.In(rights, zone);
            RequireArgument.IsAnything(rights); // we don't do a lookup based on it so the method will just return false if the flags are invalid
            RequireArgument.IsOneOf("zone", zone, RightsZone.Internet, RightsZone.Trusted, RightsZone.BlinkBox);

            if (this.rightsByZone.ContainsKey(zone))
            {
                var zoneRights = this.rightsByZone[zone];
                foreach (var partTypeAndRights in zoneRights)
                {
                    if ((partTypeAndRights.Value & rights) == rights)
                    {
                        yield return partTypeAndRights.Key;
                    }
                }
            }

            BlinkBoxTrace.Out();
        }

        /// <summary>
        /// Gets the shared <see cref="ContentRights"/> for one or more <see cref="ContentPartType"/>s in a specified <see cref="RightsZone"/>;
        /// that is the rights that all of the part types have.
        /// </summary>
        /// <param name="zone">
        /// The <see cref="RightsZone"/> in which the parts are being used.
        /// </param>
        /// <param name="partTypes">
        /// The <see cref="ContentPartType"/>s to get the effective <see cref="ContentRights"/> for.
        /// </param>
        /// <returns>
        /// The effective <see cref="ContentRights"/> for <paramref name="partTypes"/>.
        /// </returns>
        /// <remarks>
        /// This method calculates the union (bitwise AND) of the rights available for the specified part types. The returned 
        /// <see cref="ContentRights"/> are available for all of the specified part types.
        /// </remarks> 
        /// <exception cref="ArgumentNullException">
        /// <paramref name="partTypes"/> is a null reference.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="zone"/> is not one of <see cref="RightsZone.Internet"/>, <see cref="RightsZone.Trusted"/> or <see cref="RightsZone.BlinkBox"/>.
        /// </exception>
        /// <example>
        /// This example shows how to determine the shared set of <see cref="ContentRights"/> for a list of <see cref="ContentPart"/>s 
        /// in the <see cref="RightsZone.BlinkBox"/> security zone.
        /// <code>
        /// <![CDATA[
        /// // the code assumes a list of ContentPart objects, parts, and an instance of StudioContentRights, contentRights  
        /// var zone = RightsZone.BlinkBox;
        /// var partTypes = from part in parts select part.Type;
        /// var sharedRights = contentRights.GetSharedRights(zone, partTypes);
        /// ]]>
        /// </code>
        /// </example>
        public ContentRights GetSharedRights(RightsZone zone, params ContentPartType[] partTypes)
        {
            BlinkBoxTrace.In(zone, partTypes);
            RequireArgument.IsOneOf("zone", zone, RightsZone.Internet, RightsZone.Trusted, RightsZone.BlinkBox);
            RequireArgument.IsNotNull("partTypes", partTypes);

            var rights = ContentRights.All;
            if (this.rightsByZone.ContainsKey(zone))
            {
                var zoneRights = this.rightsByZone[zone];
                foreach (var partType in partTypes)
                {
                    if (zoneRights.ContainsKey(partType))
                    {
                        rights &= zoneRights[partType];
                    }
                    else
                    {
                        rights = ContentRights.None;
                        break;
                    }
                }
            }
            else
            {
                rights = ContentRights.None;
            }

            return BlinkBoxTrace.Out(rights);
        }

        /// <summary>
        /// Determines whether at least one of the <see cref="ContentPartType"/>s have the specified <see cref="ContentRights"/> in a <see cref="RightsZone"/>.
        /// </summary>
        /// <param name="rights">
        /// The <see cref="ContentRights"/> to check for.
        /// </param>
        /// <param name="zone">
        /// The <see cref="RightsZone"/> in which the parts are being used.
        /// </param>
        /// <param name="partTypes">
        /// The <see cref="ContentPartType"/>s to get the effective <see cref="ContentRights"/> for.
        /// </param>
        /// <returns>
        /// <c>true</c> if the <paramref name="partTypes"/> have the specified <paramref name="rights"/>; otherwise <c>false</c>.
        /// </returns>
        /// <remarks>
        /// This method is a wrapper around <see cref="GetAvailableRights"/> which simplifies the check for rights availability.
        /// For a given set of <see cref="StudioContentRights"/>, <c>contentRights</c>, the following statements are equivalent:
        /// <code>
        /// <![CDATA[
        /// contentRights.HasAvailableRights(ContentRights.Watch, RightsZone.Internet, ContentPartType.Trailer, ContentPartType.Clip);
        /// ]]>
        /// </code>
        /// And:
        /// <code>
        /// <![CDATA[
        /// (contentRights.GetAvailableRights(RightsZone.Internet, ContentPartType.Trailer, ContentPartType.Clip) & ContentRights.Watch) != 0;
        /// ]]>
        /// </code>
        /// </remarks> 
        /// <exception cref="ArgumentNullException">
        /// <paramref name="partTypes"/> is a null reference.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="zone"/> is not one of <see cref="RightsZone.Internet"/>, <see cref="RightsZone.Trusted"/> or <see cref="RightsZone.BlinkBox"/>.
        /// </exception>
        /// <seealso cref="GetAvailableRights"/>
        /// <seealso cref="HasSharedRights"/>
        /// <example>
        /// This example shows how to determine whether a <see cref="BlinkAsset"/> may be created from at least one of a list of 
        /// <see cref="ContentPart"/>s in the <see cref="RightsZone.BlinkBox"/> security zone.
        /// <code>
        /// <![CDATA[
        /// // the code assumes a list of ContentPart objects, parts, and an instance of StudioContentRights, contentRights  
        /// var rights = ContentRights.CreateBlink;
        /// var zone = RightsZone.BlinkBox;
        /// var partTypes = from part in parts select part.Type;
        /// var canBlinkFromAtLeastOnePart = contentRights.HasAvailableRights(rights, zone, partTypes);
        /// ]]>
        /// </code>
        /// </example>
        public bool HasAvailableRights(ContentRights rights, RightsZone zone, params ContentPartType[] partTypes)
        {
            BlinkBoxTrace.In(zone, partTypes);
            RequireArgument.IsAnything(rights); // we don't do a lookup based on it so the method will just return false if the flags are invalid
            RequireArgument.IsOneOf("zone", zone, RightsZone.Internet, RightsZone.Trusted, RightsZone.BlinkBox);
            RequireArgument.IsNotNull("partTypes", partTypes);

            var result = (this.GetAvailableRights(zone, partTypes) & rights) == rights;

            return BlinkBoxTrace.Out(result);
        }

        /// <summary>
        /// Determines whether all of the specified <see cref="ContentPartType"/>s have the specified <see cref="ContentRights"/> in a <see cref="RightsZone"/>.
        /// </summary>
        /// <param name="rights">
        /// The <see cref="ContentRights"/> to check for.
        /// </param>
        /// <param name="zone">
        /// The <see cref="RightsZone"/> in which the parts are being used.
        /// </param>
        /// <param name="partTypes">
        /// The <see cref="ContentPartType"/>s to get the effective <see cref="ContentRights"/> for.
        /// </param>
        /// <returns>
        /// <c>true</c> if the <paramref name="partTypes"/> have the specified <paramref name="rights"/>; otherwise <c>false</c>.
        /// </returns>
        /// <remarks>
        /// This method is a wrapper around <see cref="GetSharedRights"/> which simplifies the check for rights availability.
        /// For a given set of <see cref="StudioContentRights"/>, <c>contentRights</c>, the following statements are equivalent:
        /// <code>
        /// <![CDATA[
        /// contentRights.HasSharedRights(ContentRights.Watch, RightsZone.Internet, ContentPartType.Trailer, ContentPartType.Clip);
        /// ]]>
        /// </code>
        /// And:
        /// <code>
        /// <![CDATA[
        /// (contentRights.GetSharedRights(RightsZone.Internet, ContentPartType.Trailer, ContentPartType.Clip) & ContentRights.Watch) != 0;
        /// ]]>
        /// </code>
        /// </remarks> 
        /// <exception cref="ArgumentNullException">
        /// <paramref name="partTypes"/> is a null reference.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="zone"/> is not one of <see cref="RightsZone.Internet"/>, <see cref="RightsZone.Trusted"/> or <see cref="RightsZone.BlinkBox"/>.
        /// </exception>
        /// <seealso cref="GetSharedRights"/>
        /// <seealso cref="HasAvailableRights"/>
        /// <example>
        /// This example shows how to determine whether a <see cref="BlinkAsset"/> may be created from all parts in a list of 
        /// <see cref="ContentPart"/>s in the <see cref="RightsZone.BlinkBox"/> security zone.
        /// <code>
        /// <![CDATA[
        /// // the code assumes a list of ContentPart objects, parts, and an instance of StudioContentRights, contentRights  
        /// var rights = ContentRights.CreateBlink;
        /// var zone = RightsZone.BlinkBox;
        /// var partTypes = from part in parts select part.Type;
        /// var canBlinkFromAllParts = contentRights.HasSharedRights(rights, zone, partTypes);
        /// ]]>
        /// </code>
        /// </example>
        public bool HasSharedRights(ContentRights rights, RightsZone zone, params ContentPartType[] partTypes)
        {
            BlinkBoxTrace.In(zone, partTypes);
            RequireArgument.IsAnything(rights); // we don't do a lookup based on it so the method will just return false if the flags are invalid
            RequireArgument.IsOneOf("zone", zone, RightsZone.Internet, RightsZone.Trusted, RightsZone.BlinkBox);
            RequireArgument.IsNotNull("partTypes", partTypes);

            var result = (this.GetSharedRights(zone, partTypes) & rights) == rights;

            return BlinkBoxTrace.Out(result);
        }

        #endregion
    }
}